# __*__ coding:utf-8 __*__
from time import sleep
import traceback

from selenium.common.exceptions import StaleElementReferenceException
from qturboframe.lib.utils.assertion import Assertion
from selenium.webdriver.common.by import By
from qturboframe.lib.utils.by import By
from qturboframe.lib.webdriver.exceptions import SeleniumError
from qturboframe.lib.ranorex.exceptions import CsClientError
from qturboframe.lib.report.log import logger
from qturboframe.public_ui.base_element import BaseElement
from qturboframe.public_ui.h_ui.switch import Switch
from qturboframe.public_ui.h_ui.input_type import InputType
from qturboframe.public_ui.h_ui.input_number import InputNumber
from qturboframe.public_ui.h_ui.type_field import TypeField
from qturboframe.public_ui.h_ui.textarea import TextArea
from qturboframe.public_ui.h_ui.select import Select
from qturboframe.public_ui.h_ui.select_tree import SelectTree
from qturboframe.public_ui.h_ui.select_table import SelectTable
from qturboframe.public_ui.h_ui.calendar import Calendar
from qturboframe.public_ui.h_ui.timer import Timer
from qturboframe.public_ui.h_ui.transfer import Transfer
from qturboframe.public_ui.h_ui.radio import Radio
from qturboframe.public_ui.h_ui.checkbox import Checkbox
from qturboframe.lib.utils.CustomError.custom_error import CustomError


class AgTable(BaseElement):
    """
    ag table表格操作

    :Attributes:
     - _driver: 控件所在的当前webdriver对象
     - _element: 控件元素webelement对象
     - timeout: 步骤操作的超时时间

    :Raises:
        当element_para类型错误时则抛出AttributeError异常

    :Example:
     - 例1: AgTable(webelement)
     - 例2: AgTable({By.CLASS_NAME:'class_name'})
     - 例3: AgTable({By.CLASS_NAME:'class_name'}, father=driver)
     - 例4: AgTable({By.CLASS_NAME:'class_name'}, father=webelement)
    """

    __RADIO_CLASS = r'h-radio-group'
    __CHECKBOX_CLASS = r'h-checkbox-group'
    __SELECT_SINGLE_CLASS = r'h-select-single'
    __SELECT_MULTIPLE_CLASS = r'h-select-multiple'
    __SELECT_TREE_SINGLE_CLASS = r'h-selectTree-single'
    __SELECT_TREE_MULTIPLE_CLASS = r'h-selectTree-multiple'
    __SELECT_TABLE_SINGLE_CLASS = r'h-selectTable-single'
    __TYPE_FIELD_CLASS = r'h-typefield'
    __INPUT_TYPE_CLASS = r'h-input-wrapper'
    __INPUT_NUMBER_CLASS = r'h-input-number'
    __CALENDAR_CLASS = r'h-date-picker'
    __GROUP_CLASS = r'h-row'

    def __init__(self, element_para, father=None, timeout=5, index=1):
        self._driver = father
        self.index = index
        self.timeout = timeout
        if isinstance(element_para, dict) and By.TEXT in element_para:
            text = element_para.get(By.TEXT)
            xpath = ".//div[contains(@class,'ag-table')]//span[contains(@class,'ag-header-cell-text') and text()='{}']//ancestor::div[contains(@class,'ag-table')]".format(
                text)
            item_by = By.XPATH
            item_value = xpath
        else:
            item_by = None
            item_value = None
            for key, value in element_para.items():
                item_by = key
                item_value = value

        self._init_ag_table(item_by, item_value)

        # self._init_ag_table_scroll_pagination()

    def load_ready(self):
        for i in range(int(self.timeout)):
            finished = True
            xpath = ".//*[contains(text(),'加载中')]"
            load_elements = self._driver.find_elements_by_attribute(By.XPATH, xpath, timeout=0.5)
            for e in load_elements:
                try:
                    if e.is_displayed():
                        logger.debug("页面正在加载中")
                        finished = False
                        sleep(1)
                        break

                except:
                    break

            if finished:
                logger.debug("页面加载完成")
                break

    def _init_ag_table(self, by, xpath, index=1):
        logger.info("获取表格对象开始")
        logger.info("确认页面是否加载完成")
        self.load_ready()
        logger.debug("查找的table为：{}：{}开始".format(by, xpath))
        self.locator_by = by
        self.locator = xpath
        temps = self._driver.find_elements_by_attribute(by, xpath, timeout=self.timeout)
        ag_tables = []
        # 有些页面并没有“加载中”字符出现，所以用下面逻辑判断页面加载完成
        retry_time = 1 if int(self.timeout) < 1 else int(self.timeout)
        for i in range(retry_time):
            for temp in temps:
                if temp.is_displayed() and temp.text != '':
                    ag_tables.append(temp)

            if len(ag_tables) == 0:
                sleep(1)
                temps = self._driver.find_elements_by_attribute(by, xpath, timeout=self.timeout)
            else:
                break

        if len(ag_tables) >= 1:
            super().__init__(ag_tables[index - 1], self._driver, self.timeout)
            self.ag_table = self.element
        else:
            message = "没有找到DOM结构为：{}的ag_table".format(self.locator)
            raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element, message)

        # #设置表格宽度为较大宽度，以便展示所有数据
        # self._driver.execute_script("arguments[0].__vue__.width='100000000';", self.ag_table)
        self._set_table_width()

        self.__get_ag_tables()

        self.pagination = None
        # self.__get_pagination()

        # self.first_row_index = self.get_first_colindex()

        # 获取到table中有viewport中的首列
        if not self.ag_pinned_left_header:
            self.first_column_index = 1
        else:
            pinned_rows = self.ag_pinned_left_header.find_elements_by_attribute(By.CSS_SELECTOR, ".ag-header-cell",
                                                                                timeout=0.5)
            self.first_column_index = len(pinned_rows) + 1
        logger.debug("查找的table为：{}：{}结束".format(by, xpath))

        logger.info("获取表格对象结束")

    def _set_table_width(self):
        # 设置表格宽度为较大宽度，以便展示所有数据
        self._driver.execute_script("arguments[0].__vue__.width='100000000';", self.ag_table)

    def _set_table_height(self):
        # 设置表格宽度为较大宽度，以便展示所有数据
        self._driver.execute_script("arguments[0].__vue__.height='100000000';", self.ag_table)

    def __get_header_rows(self):
        '''
        用于获取表格头的行数，之后用于判断单行表头和双行表头
        '''
        locator = "div.ag-header-row"
        header_rows = self.ag_pinned_left_header.find_elements_by_attribute(By.CSS_SELECTOR, locator)
        return len(header_rows)

    def __get_ag_tables(self):
        '''
        获取表格中各部分表格
        '''
        # 左边固定的表头table
        locator = ".ag-header .ag-pinned-left-header"
        self.ag_pinned_left_header = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)

        # 中间的表头table
        locator = ".ag-header .ag-header-viewport"
        self.ag_header_viewport = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)

        # 左边固定的body table
        # body table
        locator = ".ag-body-viewport .ag-pinned-left-cols-container"
        self.ag_body_pinned_left = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)

        # 中间的body talbe
        locator = ".ag-body-viewport .ag-center-cols-clipper"
        self.ag_body_center = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)

    def get_rows_number(self):
        total_row_number = 0
        if self.pagination:
            # 获取到有分页控件表格页面上数据的总数
            page_total_locator = "span.o-page-total"
            page_total_element = self.pagination.exist(By.CSS_SELECTOR, page_total_locator, timeout=1)
            text = page_total_element.text
            return int(text.replace('共', '').replace('条', ''))
        else:
            # 页面没有分页标签
            # 页面需要滚动滚动条才能显示出页面所有数据的总数
            self.__scroll_height_scoller(0)
            before_top = -1
            height_index = 1
            while True:
                before_top = self.__get_scroll_height_top()

                scroll_height = self.__get_height_scoller_height()
                self.__scroll_height_scoller(scroll_height * height_index)
                current_top = self.__get_scroll_height_top()
                if current_top == before_top:
                    # 获取到不能滚动时的列，然后获取到最大的row-index
                    locator = ".ag-row"
                    # 获取到显示的所有的行
                    rows = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, locator)
                    if len(rows) == 0:
                        total_row_number = 0
                    else:
                        total_row_number = self.__get_row_index(rows)[-1] + 1
                    break
                height_index = height_index + 1

        return total_row_number

    def __get_row_index(self, rows):
        row_index_list = []
        for row in rows:
            row_index = row.get_attribute("row-index")
            row_index_list.append(int(row_index))

        row_index_list.sort()
        return row_index_list

    def get_group_headers(self):
        self._set_table_width()
        locator = ".ag-header-row-column-group .ag-header-text-center"
        first_group_headers = self.ag_header_viewport.find_elements_by_attribute(By.CSS_SELECTOR, locator,
                                                                                 timeout=self.timeout)
        colspan_list = []
        group_header_list = []
        for gh in first_group_headers:
            aria_index = gh.get_attribute("aria-colspan")
            if aria_index is None:
                aria_index = 1
            colspan_list.append(int(aria_index))
            group_header_list.append(gh.get_attribute("innerText"))

        locator = ".ag-header-row-column"
        # 第二行表头
        header_row_column = self.ag_header_viewport.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)
        header_row_column_text_list = header_row_column.get_attribute("innerText").split("\n")

        self.all_headers = []
        start = 0
        for i in range(len(group_header_list)):
            colspane = colspan_list[i]
            group_header = group_header_list[i]
            for j in range(colspane):
                # 两个表头组合成的表头
                header_text = group_header + "/" + header_row_column_text_list[j + start]
                self.all_headers.append(header_text)
            start = start + colspane

        logger.debug("表格的表头信息为：{}".format(self.all_headers))

    def get_expected_rows(self, header_content: dict, select_all=False):
        '''
        根据列名和列名信息查找ag的表格中所在的行数,字典key为table为列名，字典值为table的内容

        :Args:
         - header_content: 字典类型，为table的某一header名和某一行的内容组成的字典例如：{'指令序号': '1311', '回溯状态': '正常'}
         - select_all: 是否选中所有符合目标的行，目前只考虑False情况

        :Returns:
            None

        :Raises:
            None
        '''
        if self.__get_header_rows() == 2:
            self.get_group_headers()
        else:
            self.get_table_headers()

        satisfied_row_indexes = self.get_expected_row_indexs(header_content, select_all=select_all)

        return satisfied_row_indexes

    def __select_row(self, satisfied_rows, select_all=False):
        if select_all:
            for satisfied_row in satisfied_rows:
                row_index = satisfied_row
                for i_scroll_times in range(3):
                    # 只选中第一个符合要求的数据
                    locator = ".ag-row[row-index='{}'] input[type='checkbox']".format(row_index)
                    checkbox = self.ag_body_pinned_left.exist(By.CSS_SELECTOR, locator, timeout=0.5)
                    if checkbox:
                        self._driver.execute_script("arguments[0].click();", checkbox)
                        break
                    else:
                        locator = ".ag-row[row-index='{}']>div:nth-child(1)".format(row_index)
                        row = self.ag_body_center.exist(By.CSS_SELECTOR, locator, timeout=0.5)
                        if row:
                            row.click()
                            break
                    # 执行到此段代码则表示没有找到想要找到的行，则需要纵向滚动
                    self.__scroll_height_scoller(self.cell_height * int(row_index))
                    sleep(1)

                logger.info("选中表格中第{}行".format(row_index))
        else:
            row_index = satisfied_rows[0]
            for i_scroll_times in range(3):
                # 只选中第一个符合要求的数据
                locator = ".ag-row[row-index='{}'] input[type='checkbox']".format(row_index)
                checkbox = self.ag_body_pinned_left.exist(By.CSS_SELECTOR, locator, timeout=0.5)
                if checkbox:
                    self._driver.execute_script("arguments[0].click();", checkbox)
                    break
                else:
                    locator = ".ag-row[row-index='{}']>div:nth-child(1)".format(row_index)
                    row = self.ag_body_center.exist(By.CSS_SELECTOR, locator, timeout=0.5)
                    if row:
                        row.click()
                        break
                # 执行到此段代码则表示没有找到想要找到的行，则需要纵向滚动
                self.__scroll_height_scoller(self.cell_height * int(row_index))
                sleep(1)

            logger.info("选中表格中第{}行".format(row_index + 1))

    def __click_cell(self, satisfied_rows, click_header_dic, switch_on=True):
        '''
        点击目标行中，某一个符合要求的单元格
        '''
        if satisfied_rows and click_header_dic:
            # 先查到要操作的数据所在的列号，例如要点击“是否可见”下面的开关按钮，先找到“是否可见”在第几列
            if self.__get_header_rows() == 2:
                self.get_group_headers()
            else:
                self.get_table_headers()

            # self.__scroll_height_scoller(0)
            row_index = satisfied_rows[0]

            # 点击目标单元格中指定的按钮，当给定的click_header_dic中key对应的value值为空时，则直接点击单元格
            click_cell_header = ""
            click_cell_button = ""
            for key, value in click_header_dic.items():
                click_cell_header = key
                click_cell_button = value.strip()

            column_index = self._get_header_index(click_cell_header) + self.first_column_index
            locator = "div[row-index='{}'] .ag-cell[aria-colindex='{}']".format(row_index, column_index)
            target_cell = self.ag_body_center.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=5)

            if click_cell_button:
                # 则点击对应的按钮
                xpath = ".//*[normalize-space(text())='{}']".format(click_cell_button)
                op_btn = target_cell.find_element_by_attribute(By.XPATH, xpath, timeout=1)
                op_btn.click()
            else:
                Switch(father=target_cell, timeout=1).switch(switch_on)
        else:
            switch_cells = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, ".h-switch", timeout=5)
            for item in switch_cells:
                Switch(element_para=item, timeout=1).switch(switch_on)

    def select_row(self, header_content: dict, select_all=False):
        '''
        根据列名和列名信息查找ag的表格中所在的行数,字典key为table为列名，字典值为table为内容

        :Args:
         - header_content: 字典类型，为table的某一header名和某一行的内容组成的字典例如：{'指令序号': '1311', '回溯状态': '正常'}

        :Returns:
            None

        :Raises:
            SeleniumError: 表格行数查找失败

        :Example:
         - 例: table.select_row({'指令序号': '1311', '回溯状态': '正常'})
        '''
        logger.info("选择表格数据开始")
        for i in range(3):  # 因由页面刷新的问题所以尝试三次
            try:
                # 先确保所有的checkbox是未选中状态
                locator = "input[type='checkbox']"
                checkbox = self.ag_pinned_left_header.exist(By.CSS_SELECTOR, locator, timeout=0.5)
                if checkbox:
                    if '(checked)' in checkbox.get_attribute("aria-label"):
                        self._driver.execute_script("arguments[0].click();", checkbox)
                    else:
                        # 先使全选中
                        self._driver.execute_script("arguments[0].click();", checkbox)
                        # 再取消选中
                        self._driver.execute_script("arguments[0].click();", checkbox)

                # 获取到满足要求的行
                satisfied_rows = self.get_expected_rows(header_content, select_all=select_all)

                if len(satisfied_rows) == 0:
                    raise SeleniumError("没有找到符合要求的列：{}".format(header_content))

                self.__select_row(satisfied_rows, select_all=select_all)
                break
            except Exception as e:
                if "Message: stale element reference: element is not attached to the page document" in str(e):
                    # logger.debug(str(e))
                    sleep(0.5)
                    self._init_ag_table(self.locator_by, self.locator, index=self.index)
                else:
                    raise SeleniumError(str(e))
        else:
            message = "因为页面刷新所以查找元素失败"
            raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element, message)
        logger.info("选择表格数据结束")

    def check(self, header_content: dict, expect_data: dict, check_delete=False):
        '''
        根据列名和列名信息查找ag的表格中所在的行数,字典key为table为列名，字典值为table为内容

        :Args:
         - header_content: 字典类型，为table的某一header名和某一行的内容组成的字典例如：{'指令序号': '1311', '回溯状态': '正常'}
         - expect_data: 期待的结果
         - check_delete: 是否为检查删除后的数据，为True检查删除后的数据，没有找到数据则成功

        :Returns:
            None

        :Raises:
            SeleniumError: 表格行数查找失败

        :Example:
         - 例: table.check({'指令序号': '1311', '回溯状态': '正常'},{'指令序号1': '13111', '回溯状态1': '正常1'})
        '''
        if len(header_content) == 0:
            logger.info("不做检查操作")
            return

        check_result = True
        logger.info("检查表格数据开始")
        logger.debug("header_content:{}".format(header_content))
        for i in range(3):
            try:
                if self.__get_header_rows() == 2:
                    self.get_group_headers()
                else:
                    self.get_table_headers()

                # 获取到满足要求的行
                satisfied_row_indexes = self.get_expected_rows(header_content, select_all=False)

                if len(satisfied_row_indexes) == 0 and not check_delete:
                    message = "没有找到符合要求的列：{}".format(header_content)
                    raise SeleniumError(CustomError.Error_EQ03007001_Table_Not_Find_Correct_Column, message)
                elif check_delete and len(satisfied_row_indexes) == 0:
                    logger.info("数据已被成功删除")
                    return
                elif check_delete and len(satisfied_row_indexes) != 0:
                    message = "数据未成功删除"
                    raise SeleniumError(CustomError.Error_EQ03007000_Table_Not_Delete_Data, message)

                check_header_list = list(expect_data.keys())
                row_index = satisfied_row_indexes[0]

                for ch in check_header_list:
                    col_index = self.first_column_index + self._get_header_index(ch)
                    locator = "div[row-index='{}'] .ag-cell[aria-colindex='{}']".format(row_index, col_index)
                    element = self.ag_body_center.find_element_by_attribute(By.CSS_SELECTOR, locator)
                    actual_content = element.text.strip()
                    expect_content = expect_data[ch]
                    if actual_content == expect_content:
                        logger.info("table中第{}行- '{}'的期待数据为:{}，实际数据为:{}".format(row_index, ch,
                                                                                expect_content,
                                                                                actual_content))
                    else:
                        check_result = False
                        logger.error("table中第{}行- '{}'的期待数据为:{}，实际数据为:{}".format(row_index, ch,
                                                                                 expect_content,
                                                                                 actual_content))

                if not check_result:
                    message = "数据验证失败"
                    raise SeleniumError(CustomError.Error_EQ03007002_Table_Verify_Data_Fail, message)

                break
            except SeleniumError or StaleElementReferenceException as e:
                if "Message: stale element reference: element is not attached to the page document" in str(e):
                    # logger.debug(str(e))
                    sleep(0.5)
                    self._init_ag_table(self.locator_by, self.locator, index=self.index)
                else:
                    raise SeleniumError(e)
            except Exception as e:
                raise SeleniumError(e)
        else:
            message = "因为页面刷新所以查找元素失败"
            raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element, message)
        logger.info("检查表格数据结束")

    def check_expected_row(self, header_content: dict, expect_row_no):
        '''
        根据列名和列名信息查找ag的表格中所在的行数,字典key为table为列名，字典值为table为内容

        :Args:
         - header_content: 字典类型，为table的某一header名和某一行的内容组成的字典例如：{'指令序号': '1311', '回溯状态': '正常'}
         - expect_row_no: 期望数据的行数

        :Returns:
            None

        :Raises:
            SeleniumError: 表格行数查找失败

        :Example:
         - 例: table.check_expected_row({'指令序号': '1311', '回溯状态': '正常'}, 1)
        '''
        if len(header_content) == 0:
            logger.info("不做检查操作")
            return
        for i in range(3):
            try:
                # 获取到满足要求的行
                satisfied_rows = self.get_expected_rows(header_content, select_all=False)
                if len(satisfied_rows) == 0:
                    message = "在当前表格并没有找到符合要求的数据：{}".format(header_content)
                    raise SeleniumError(CustomError.Error_EQ03007003_Not_Find_Correct_Row, message)
                actual_row_no = satisfied_rows[0] + 1
                if actual_row_no == expect_row_no:
                    logger.info("期望‘{}’数据在第'{}'行".format(header_content, expect_row_no))
                    logger.info("实际‘{}’数据在第'{}'行".format(header_content, actual_row_no))
                else:
                    logger.error("期望‘{}’数据在第'{}'行".format(header_content, expect_row_no))
                    logger.error("实际‘{}’数据在第'{}'行".format(header_content, actual_row_no))
                    raise SeleniumError("数据校验失败")
                break
            except SeleniumError or StaleElementReferenceException as e:
                if "Message: stale element reference: element is not attached to the page document" in str(e):
                    # logger.debug(str(e))
                    sleep(0.5)
                    self._init_ag_table(self.locator_by, self.locator, index=self.index)
                else:
                    raise SeleniumError(e)
            except Exception as e:
                raise SeleniumError(e)
        else:
            raise SeleniumError("因为页面刷新所以查找元素失败")

    def check_table_row_number(self, expected_row_num):
        '''
        获取table中数据的行数

        :Args:
            None

        :Returns:
            None

        :Raises:
            SeleniumError: 表格行数查找失败/因页面刷新导致元素查找失败

        :Example:
         - 例: table.check_table_row_number(10)
        '''
        for i in range(3):
            try:
                actual_row_number = self.get_rows_number()
                if int(expected_row_num) != int(actual_row_number):
                    error_msg = "行数期望值和实际值不相等，期望值为：“{}”，实际值为:“{}”".format(expected_row_num, actual_row_number)
                    raise SeleniumError(CustomError.Error_EQ03007004_Table_Compare_Rows_Faile, extra_msg=error_msg)
                else:
                    logger.info("期望行数为：{}".format(expected_row_num))
                    logger.info("实际行数为：{}".format(actual_row_number))
                break
            except SeleniumError or StaleElementReferenceException as e:
                if "Message: stale element reference: element is not attached to the page document" in str(e):
                    # logger.debug(str(e))
                    sleep(0.5)
                    self._init_ag_table(self.locator_by, self.locator, index=self.index)
                else:
                    raise SeleniumError(e)
            except Exception as e:
                raise SeleniumError(e)
        else:
            message = "因为页面刷新所以查找元素失败"
            raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element, message)

    def click_row(self, header_content: dict = [], click_header_dic: dict = [], switch_on=True):
        '''
        根据列名和列名信息查找ag的表格中所在的行数,字典key为table为列名，字典值为table为内容

        :Args:
         - header_content: 字典类型，为table的某一header名和某一行的内容组成的字典例如：{'指令序号': '1311', '回溯状态': '正常'}

        :Returns:
            None

        :Raises:
            SeleniumError: 表格行数查找失败

        :Example:
         - 例: table.select_row({'指令序号': '1311', '回溯状态': '正常'})
        '''
        logger.info("点击表格数据行开始")
        for i in range(3):
            try:
                # 先确保所有的checkbox是未选中状态
                locator = "input[type='checkbox']"
                checkbox = self.ag_pinned_left_header.exist(By.CSS_SELECTOR, locator, timeout=0.5)
                if checkbox:
                    if '(checked)' in checkbox.get_attribute("aria-label"):
                        self._driver.execute_script("arguments[0].click();", checkbox)
                    else:
                        # 先使全选中
                        self._driver.execute_script("arguments[0].click();", checkbox)
                        # 再取消选中
                        self._driver.execute_script("arguments[0].click();", checkbox)

                satisfied_rows = []
                if header_content:
                    # 获取到满足要求的行
                    satisfied_rows = self.get_expected_rows(header_content)

                    if len(satisfied_rows) == 0:
                        message = "没有找到符合要求的列：{}".format(header_content)
                        raise SeleniumError(CustomError.Error_EQ03007001_Table_Not_Find_Correct_Column, message)

                self.__click_cell(satisfied_rows, click_header_dic, switch_on)
                break
            except SeleniumError or StaleElementReferenceException as e:
                if "Message: stale element reference: element is not attached to the page document" in str(e):
                    # logger.debug(str(e))
                    sleep(0.5)
                    self._init_ag_table(self.locator_by, self.locator, index=self.index)
                else:
                    raise SeleniumError(e)
            except Exception as e:
                raise SeleniumError(e)
        else:
            message = "因为页面刷新所以查找元素失败"
            raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element, message)

        logger.info("点击表格数据行结束")

    def click_target_row(self, row_number):
        '''
        点击table的指定列（row_number)

        :Args:
         - row_number: 正数类型，要点击的列号

        :Returns:
            None

        :Raises:
            CsClientError: 表格无数据/要点击的列超过了table现有数据范围

        :Example:
         - 例: table.select_row({'指令序号': '1311', '回溯状态': '正常'})
        '''
        logger.info("点击表格第{}列开始".format(row_number))
        rows = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, "div[role='row']")
        if not rows:
            message = "表格无数据"
            raise SeleniumError(CustomError.Error_EQ03007005_Table_No_Data_In_Table, message)

        if len(rows) < row_number:
            message = "当前table有{}行数据，要点击的{}行不在范围内".format(len(rows), row_number)
            raise SeleniumError(CustomError.Error_EQ03007006_Table_Data_Not_Correct, message)

        rows[row_number - 1].find_elements_by_attribute(By.CSS_SELECTOR, ".ag-cell")[0].click()
        logger.info("点击表格第{}列结束".format(row_number))

    def check_tablefull_data(self, check_type, expected_data, tablename="table"):
        # self.all_header_cell_info = {}#字典的键值为列名{text:[text, width, left, colindex, rowindex]}
        # self.all_header_cell_info_colindex = {}#字典的键值为列号 {colindex:[text, width, left, colindex, rowindex]}
        # self.colindex_list = [] #存储字典的键值
        # 如果最后一列为空，则去掉
        if not self.all_headers[-1]:
            self.all_headers = self.all_headers[0:-1]

        if check_type.lower().strip() == "tablecheck_full":
            # 查找到期望数据中比实际表头多的数据
            need_check_headers = [e for e in expected_data if e not in self.all_headers]
            if len(need_check_headers) != 0:
                logger.error("请确认这些表头信息是否正确：{}, 这些表头信息在当前表格{}中不存在".format(need_check_headers, tablename))
                raise SeleniumError(CustomError.Error_EQ03007007_Table_Header_Not_Correct,"tablecheck_full,但是输入的表头信息和实际的表头信息不符")

            # 查找出期望数据比实际表头少的数据
            need_check_headers = [e for e in self.all_headers if e not in expected_data]
            if len(need_check_headers) != 0:
                logger.error("请确认这些表头信息是否正确：{}，期望数据中没有这些表头的相关信息".format(need_check_headers))
                raise SeleniumError(CustomError.Error_EQ03007007_Table_Header_Not_Correct,"tablecheck_full,但是输入的表头信息和实际的表头信息不符")

    def check_table_data_full(self, header_content=[], expected_data_list=[], check_type="", need_query=False,
                              tablename="table"):
        '''
        检查表格中的数据

        :Args:
         - header_content: 字典类型，为table的某一header名和某一行的内容组成的字典例如：{'指令序号': '1311', '回溯状态': '正常'}，当need_query为False时此值为空
         - expected_data_list: 期待的结果，数据格式为 [{"指令批号": "332", "指令序号": "nocheck", "指令完成情况": "已分发"}
                                                    ,{"指令批号": "331", "指令序号": "331", "指令完成情况": "已分发"}]
         - check_type: 为tablecheck_full和其他，当是tablecheck_full时，则检查表格整行数据
         - need_query：是否需要根据header_content的内容去先查询到目标行，当数据较多时，建议使用页面具有的搜索功能，先搜索出要查找的数据，这样可以提高检查效率

        :Returns:
            None

        :Raises:
            SeleniumError: 表格行数查找失败

        :Example:
         - 例: table.check({'指令序号': '1311', '回溯状态': '正常'},{'指令序号1': '13111', '回溯状态1': '正常1'})
        '''
        if not expected_data_list:
            message = "请检查期望（expected_data_list）数据，此数据为空"
            raise SeleniumError(CustomError.Error_EQ03008000_Expected_Data_Is_Empty, message)

        e_error = ""
        for i in range(3):
            try:
                if need_query:
                    logger.info("获取到表头信息 - 开始")
                    self.get_table_headers()
                    logger.info("获取到表头信息 - 结束")
                    logger.info("获取的表头信息为：{}".format(self.all_headers))

                    # 检查数据格式
                    self.check_tablefull_data(check_type, expected_data_list[0])

                    logger.info("查找期望检查的数据行数 - 开始")
                    expected_rowindex_list = self.get_expected_rows(header_content)
                    logger.info("查找期望检查的数据行数 - 结束")
                    logger.info("期望检查的数据行为:{}".format(expected_rowindex_list))

                    if len(expected_rowindex_list) == 0:
                        message = "在当前表格并没有找到符合要求的数据：{}".format(header_content)
                        raise SeleniumError(CustomError.Error_EQ03007003_Not_Find_Correct_Row, message)

                    logger.info("获取到检查的数据行整行数据 - 开始")
                    actual_data_list = self.get_table_content(expected_rowindex_list)
                    logger.info("获取到检查的数据行整行数据 - 结束")
                    logger.info("获取实际数据为:{}".format(actual_data_list))

                    logger.info("比较数据 - 开始")
                    self._check_full_data(expected_data_list, actual_data_list, tablename=tablename,
                                          check_type=check_type)
                    logger.info("比较数据 - 结束")
                else:
                    logger.info("获取表头和表体内容 - 开始")
                    actual_data_list = self.get_table_all_content(expected_data_list)
                    logger.info("获取表头和表体内容 - 结束")
                    logger.info("获取的表头数据为：{}".format(self.all_headers))
                    logger.info("获取的表体数据为：{}".format(actual_data_list))

                    logger.info("检查数据格式开始 - 开始")
                    # 检查数据格式
                    self.check_tablefull_data(check_type, expected_data_list[0], tablename=tablename)
                    logger.info("检查数据格式结束 - 开始")

                    logger.info("比较数据 - 开始")
                    self._check_full_data(expected_data_list, actual_data_list, tablename=tablename,
                                          check_type=check_type)
                    logger.info("比较数据 - 结束")
                break
            except Exception as e:
                if "Message: stale element reference: element is not attached to the page document" in str(e) \
                        or "元素刷新超时！" in str(e):
                    # self.__scroll_horizontal_scoller(0)
                    # self.__scroll_height_scoller(0)
                    # sleep(1)
                    import traceback
                    logger.debug(str(e))
                    logger.debug(traceback.format_exc())
                    logger.debug("重新获取table信息")
                    self._init_ag_table(self.locator_by, self.locator, index=self.index)
                    e_error = e
                else:
                    import traceback
                    exception_message = e
                    if traceback.print_exc():
                        exception_message = exception_message + str(traceback.print_exc())
                    raise SeleniumError(exception_message)
        else:
            raise SeleniumError(e_error)

    def get_table_headers(self):
        '''
        获取到表头的内容（目前只获取到单列表头）

        :Example:
         - 例: ag_table.get_table_headers()
        '''
        self._set_table_width()
        # 获取到所有的表头信息
        locator = ".ag-header-row"
        ag_header_row = self.ag_header_viewport.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)
        inner_text = ag_header_row.get_attribute("innerText")
        self.all_headers = inner_text.split("\n")
        logger.debug("表格的表头信息为：{}".format(self.all_headers))

    def select_auto_fill(self, header_content: dict, fill_content: dict):
        '''
        根据列名和列名信息查找ag的表格中所在的行数,字典key为table为列名，字典值为table为内容

        :Args:
         - header_content: 字典类型，为table的某一header名和某一行的内容组成的字典例如：{'指令序号': '1311', '回溯状态': '正常'}

        :Returns:
            None

        :Raises:
            SeleniumError: 表格行数查找失败

        :Example:
         - 例: ag_table().select_auto_fill({"产品名称":"基金恒业1"}
                                               ,{"卖出数量(万元)":"1.45"
                                                 ,"目标收益率(%)":"100"})
        '''
        logger.info("填写ag_table表格数据开始")
        for i in range(3):
            try:
                # 先确保所有的checkbox是未选中状态
                locator = "input[type='checkbox']"
                checkbox = self.ag_pinned_left_header.exist(By.CSS_SELECTOR, locator)
                if checkbox:
                    if '(checked)' in checkbox.get_attribute("aria-label"):
                        self._driver.execute_script("arguments[0].click();", checkbox)
                    else:
                        # 先使全选中
                        self._driver.execute_script("arguments[0].click();", checkbox)
                        # 再取消选中
                        self._driver.execute_script("arguments[0].click();", checkbox)

                # 获取到满足要求的行
                satisfied_rows = self.get_expected_rows(header_content, select_all=False)

                if len(satisfied_rows) == 0:
                    raise SeleniumError(CustomError.Error_EQ03007001_Table_Not_Find_Correct_Column,"没有找到符合要求的列：{}".format(header_content))

                # 选中行
                self.__select_row(satisfied_rows, select_all=False)

                # 根据fill_dic中的数据进行填充
                if self.__get_header_rows() == 2:
                    self.get_group_headers()
                else:
                    self.get_table_headers()

                query_header_indexes = self.get_expected_row_indexs(fill_content)

                self.__fill_table(satisfied_rows, query_header_indexes, fill_content)
                break
            except SeleniumError or StaleElementReferenceException as e:
                if "Message: stale element reference: element is not attached to the page document" in str(e):
                    # logger.debug(str(e))
                    sleep(0.5)
                    self._init_ag_table(self.locator_by, self.locator, index=self.index)
                else:
                    raise SeleniumError(e)
            except Exception as e:
                raise SeleniumError(e)
        else:
            message = "因为页面刷新所以查找元素失败"
            raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element, message)
        logger.info("填写ag_table表格数据结束")

    def __fill_table(self, satisfied_rows, fill_content):
        for key, value in fill_content.items():
            row_index = satisfied_rows[0]

            col_index = self.all_headers[key]

            # 只选中第一个符合要求的数据
            css_selector = ".ag-row[row-index='{}'] div[aria-colindex='{}']>div".format(row_index, col_index)
            self._control_div = self.ag_body_center.find_element_by_attribute(By.CSS_SELECTOR, css_selector)

            flag = ''
            header_text = key
            value = fill_content[header_text].split(',')
            # 确定类型
            control_type_list = self.__confirm_type()
            if len(control_type_list) > 1:
                templist = list()
                for i in range(0, len(control_type_list)):
                    templist.append([value[i]])
                value = templist
            elif len(control_type_list) == 0:
                continue
            else:
                value = [value]
            # 识别控件并执行
            for i, control_type in enumerate(control_type_list):
                # 根据控件类型选择控件操作
                if control_type == self.__INPUT_TYPE_CLASS:
                    self.__deal_input_type(key, value[i], flag)
                elif control_type == self.__INPUT_NUMBER_CLASS:
                    self.__deal_input_number(key, value[i], flag)
                elif control_type == self.__TYPE_FIELD_CLASS:
                    self.__deal_type_field(key, value[i], flag)
                elif control_type == self.__SELECT_SINGLE_CLASS:
                    self.__deal_select_single(key, value[i], flag)
                elif control_type == self.__SELECT_MULTIPLE_CLASS:
                    self.__deal_select_multiple(key, value[i], flag)
                elif control_type == self.__SELECT_TREE_SINGLE_CLASS:
                    self.__deal_select_tree_single(key, value[i], flag)
                elif control_type == self.__SELECT_TREE_MULTIPLE_CLASS:
                    self.__deal_select_tree_multiple(key, value[i], flag)
                elif control_type == self.__SELECT_TABLE_SINGLE_CLASS:
                    self.__deal_select_table_single(key, value[i], flag)
                elif control_type == self.__RADIO_CLASS:
                    self.__deal_radio(key, value[i], flag)
                elif control_type == self.__CHECKBOX_CLASS:
                    self.__deal_checkbox(key, value[i], flag)
                elif control_type == self.__CALENDAR_CLASS:
                    i_icon = self._elements.find_element_by_attribute(
                        'css selector', 'div.h-date-picker-rel i.h-icon', timeout=self.timeout)
                    icon_class_list = i_icon.get_attribute('class').split(' ')
                    if 'h-icon-time' in icon_class_list:
                        self.__deal_timer(key, value[i], flag)
                    elif 'h-icon-activity' in icon_class_list:
                        self.__deal_calendar(key, value[i], flag)

    def __confirm_type(self):
        # 获取控件层div的class属性
        div_class_list = self._control_div.get_attribute('class').split(' ')
        # 判定控件类型
        if self.__INPUT_TYPE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__INPUT_TYPE_CLASS]
        elif self.__INPUT_NUMBER_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__INPUT_NUMBER_CLASS]
        elif self.__TYPE_FIELD_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__TYPE_FIELD_CLASS]
        elif self.__SELECT_SINGLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_SINGLE_CLASS]
        elif self.__SELECT_MULTIPLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_MULTIPLE_CLASS]
        elif self.__SELECT_TREE_SINGLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_TREE_SINGLE_CLASS]
        elif self.__SELECT_TREE_MULTIPLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_TREE_MULTIPLE_CLASS]
        elif self.__SELECT_TABLE_SINGLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_TABLE_SINGLE_CLASS]
        elif self.__CALENDAR_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__CALENDAR_CLASS]
        elif self.__RADIO_CLASS in div_class_list:
            self._elements = self._control_div.find_elements_by_attribute(
                'tag name', 'input', timeout=self.timeout)
            return [self.__RADIO_CLASS]
        elif self.__CHECKBOX_CLASS in div_class_list:
            self._elements = self._control_div.find_elements_by_attribute(
                'tag name', 'input', timeout=self.timeout)
            return [self.__CHECKBOX_CLASS]
        elif self.__GROUP_CLASS in div_class_list:
            templist = list()
            sub_divs = self._control_div.find_elements_by_attribute(
                'xpath', './div/div', timeout=self.timeout)
            for i in range(0, len(sub_divs)):
                self._control_div = sub_divs[i]
                templist.extend(self.__confirm_type())
            return templist
        else:
            logger.warn('非表单控件')
            return []

    def __deal_input_type(self, key, value, flag=''):
        '''
        处理hui文本框

        :Args:
         - key: 控件名称
         - value: 填充值
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 填充或判断值是出错
        '''
        input_type = InputType(self._elements, timeout=self.timeout)
        text = value[0]
        if flag == '':
            logger.info(key + '进行文本框填充。')
            input_type.set_value(text)
            return
        else:
            if 'R' in flag:
                if input_type.is_readonly():
                    logger.info(key + '只读检查通过。')
                else:
                    message = key + '只读检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'D' in flag:
                if input_type.is_default(text):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if input_type.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    input_type.set_value(text)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_input_number(self, key, value, flag=''):
        '''
        处理hui数字框

        :Args:
         - key: 控件名称
         - value: 填充值
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 填充或判断值是出错
        '''
        input_number = InputNumber(self._elements, timeout=self.timeout)
        text = value[0]
        if flag == '':
            logger.info(key + '进行数字框填充。')
            input_number.set_value(text)
            return
        else:
            if 'R' in flag:
                if input_number.is_readonly():
                    logger.info(key + '只读检查通过。')
                else:
                    message = key + '只读检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'D' in flag:
                if input_number.is_default(text):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if input_number.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    input_number.set_value(text)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_type_field(self, key, value, flag=''):
        '''
        处理hui金额框

        :Args:
         - key: 控件名称
         - value: 填充值
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 填充或判断值是出错
        '''
        type_field = TypeField(self._elements, timeout=self.timeout)
        text = value[0]
        if flag == '':
            logger.info(key + '进行文本框填充。')
            type_field.set_value(text)
        else:
            if 'R' in flag:
                if type_field.is_readonly():
                    logger.info(key + '只读检查通过。')
                else:
                    message = key + '只读检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'D' in flag:
                if type_field.is_default(text):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if type_field.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    type_field.set_value(text)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_textarea(self, key, value, flag=''):
        '''
        处理hui多行文本框

        :Args:
         - key: 控件名称
         - value: 填充值
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 填充或判断值是出错
        '''
        textarea = TextArea(self._elements, timeout=self.timeout)
        text = value[0]
        if flag == '':
            logger.info(key + '进行文本框填充。')
            textarea.set_value(text)
            return
        else:
            if 'R' in flag:
                if textarea.is_readonly():
                    logger.info(key + '只读检查通过。')
                else:
                    message = key + '只读检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'D' in flag:
                if textarea.is_default(text):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if textarea.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    textarea.set_value(text)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_radio(self, key, value, flag=''):
        '''
        处理hui单选按钮

        :Args:
         - key: 控件名称
         - value: 选择项
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        radio = Radio(self._elements, timeout=self.timeout)
        text = value[0]
        if flag == '':
            logger.info(key + '进行单选按钮选择。')
            radio.set_value(text)
        else:
            if 'D' in flag:
                if radio.is_default(text):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_checkbox(self, key, value, flag=''):
        '''
        处理hui复选按钮

        :Args:
         - key: 控件名称
         - value: 选择项
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        checkbox = Checkbox(self._elements, timeout=self.timeout)
        text = value
        if flag == '':
            logger.info(key + '进行复选框按钮选择。')
            checkbox.set_value(text)
        else:
            if 'D' in flag:
                if checkbox.is_default(text):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_select_single(self, key, value, flag=''):
        '''
        处理hui单选下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select = Select(self._elements, timeout=self.timeout)
        text = value[0]
        if flag == '':
            logger.info(key + '进行单选下拉框选择。')
            select.set_value_for_single(text, self.timeout)
        else:
            if 'D' in flag:
                if select.is_default_for_single(value[0]):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if select.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select.set_value_for_single(text, self.timeout)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_select_multiple(self, key, value, flag=''):
        '''
        处理hui多选下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select = Select(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行复选下拉框选择。')
            select.set_value_for_multiple(value)
        else:
            if 'D' in flag:
                if select.is_default_for_multiple(value):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if select.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select.set_value_for_multiple(value)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_select_tree_single(self, key, value, flag=''):
        '''
        处理hui单选下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select_tree = SelectTree(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行单选树形下拉框选择。')
            select_tree.set_value_for_single(value[0], self.timeout)
        else:
            if 'D' in flag:
                if select_tree.is_default(value[0]):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if select_tree.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select_tree.set_value_for_single(value[0], self.timeout)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_select_tree_multiple(self, key, value, flag=''):
        '''
        处理hui多选下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select_tree = SelectTree(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行复选树形下拉框选择。')
            select_tree.set_value_for_multiple(value)
        else:
            if 'D' in flag:
                if select_tree.is_default(','.join(value)):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if select_tree.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select_tree.set_value_for_multiple(value)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_select_table_single(self, key, value, flag=''):
        '''
        处理hui单选下拉表格框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select_table = SelectTable(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行复选树形下拉框选择。')
            select_table.set_value_for_single(value[0], self.timeout)
        else:
            if 'D' in flag:
                if select_table.is_default_for_single(value[0]):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if select_table.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select_table.set_value_for_single(value[0], self.timeout)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_calendar(self, key, value, flag=''):
        '''
        处理hui日历下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        calendar = Calendar(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行日期选择。')
            calendar.set_value(value[0])
        else:
            if 'D' in flag:
                if calendar.is_default(value[0]):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if calendar.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    calendar.set_value(value[0])
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_timer(self, key, value, flag=''):
        '''
        处理hui时间下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        timer = Timer(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行时间选择。')
            timer.set_value(value[0])
        else:
            if 'D' in flag:
                if timer.is_default(value[0]):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if timer.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    timer.set_value(value[0])
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_transfer(self, key, value, flag=''):
        '''
        处理hui穿梭框

        :Args:
         - key: 控件名称
         - value: 穿梭框选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        transfer = Transfer(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行穿梭框值选择。')
            transfer.set_value_for_multiple(value)
        else:
            if 'D' in flag:
                if transfer.is_default(value):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __get_last_colindex(self):
        # 判断是否滚动到最后一列
        locator = ".ag-header-row .ag-header-cell"
        ag_header_cells = self.ag_header_viewport.find_elements_by_attribute(By.CSS_SELECTOR, locator, timeout=1)
        # 获取到最后一个元素的aria-colindex属性
        last_colindex = ag_header_cells[-1].get_attribute("aria-colindex").strip()
        if last_colindex.isdigit():
            last_colindex = int(last_colindex)
        else:
            raise CsClientError("获取到当前表格最后一列的列号数据错误，实际的列号数据为:{}".format(last_colindex))
        return last_colindex

    def __get_max_colindex(self, expected_data_list):
        logger.debug("获取被查询table的最大的列号开始")
        # 获取到需要检查数据的列数
        if len(expected_data_list) > 0:
            if type(expected_data_list[0]) != dict:
                raise CsClientError("期望数据格式不正确，期望数据格式应该为字典组成的列表：{}".format(expected_data_list))
        else:
            raise CsClientError("期望数据格式不正确，期望数据为空:{}".format(expected_data_list))

        actual_column_num = 0
        for k, v in expected_data_list[0].items():
            actual_column_num += 1

        max_colindex = 0
        logger.debug("scroll_height:{}".format(self.scroll_height))
        if self.scroll_height > 0:  # 当scroll height大于0时则表明此table具有横向滚动条，需要滚动
            # 滚动滚动条到最右边，获取到table的最大列数
            self.__scroll_horizontal_scoller(self.scroll_container_width)
            sleep(0.5)
            last_colindex = self.__get_last_colindex()
            if actual_column_num > last_colindex:
                logger.debug("last colindex:{}, actual_column_num:{}".format(last_colindex, actual_column_num))
                sleep(1)  # 如果不设置此超时，则获取的列号并不是最后一列
                # 在有的机器上无法滑动到左后一列,所以多加了下面的滑动
                logger.debug("horizontal scroll 1111")
                self.__scroll_horizontal_scoller(int(self.scroll_container_width / 2))
                self.__scroll_horizontal_scoller(self.scroll_container_width)
                sleep(1)  # 如果不设置此超时，则获取的列号并不是最后一列
                last_colindex = self.__get_last_colindex()
        else:
            last_colindex = self.__get_last_colindex()
        max_colindex = last_colindex
        logger.debug("获取被查询table的最大的列号结束，被查询table的最大列号为:{}".format(max_colindex))
        return max_colindex

    def get_first_colindex(self, row_number=1):
        logger.debug("获取首行行号开始")
        # 获取首行的行号
        first_rowindex = 0
        for retry_i in range(3):
            try:
                # 获取表体的内容
                # 1. 获取当前的所有行数
                rows = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, "div[role='row']")
                if not rows:
                    # return "表格无数据"
                    raise CsClientError(CustomError.Error_EQ03007005_Table_No_Data_In_Table,"表格无数据")

                if len(rows) < row_number:
                    messsage = "期望比较的数据行数为：{}行，但是实际的数据行数为:{}".format(row_number, len(rows))
                    raise CsClientError(CustomError.Error_EQ03007006_Table_Data_Not_Correct, extra_msg=messsage)
                row = rows[0]
                first_rowindex = int(row.get_attribute("aria-rowindex"))
                break
            except StaleElementReferenceException as e:
                # StaleElementReferenceException: Message: stale element reference: element is not attached to the page document
                import traceback
                logger.debug(traceback.format_exc())
                retry_exception = str(e)
                sleep(1)
        else:
            raise CsClientError(retry_exception)
        logger.debug("获取首行行号结束，被查询table的首行序号位：{}".format(first_rowindex))
        return first_rowindex

    def get_table_all_content(self, expected_data_list):
        '''
        获取到表头的内容（包括表头和表体）

        :Example:
         - 例: ag_table.get_table_all_content()
        '''
        self.all_headers = []
        all_row_info = []
        row_number = len(expected_data_list)  # 获取需要检查数据的行数
        all_row_info = []

        # 获取table的viewport中第一行行号
        first_rowindex = self.get_first_colindex(row_number)

        current_first_colindex = self.first_column_index

        # 获取到所有的表头信息
        self.get_table_headers()

        for index in range(row_number):
            logger.debug("first_rowindex:{},index:{}".format(type(first_rowindex), type(index)))
            rowindex = first_rowindex + index

            error_message = ""
            cells = []
            for i_retry in range(3):
                one_row_info = []  # 列表的第一个值为行号，
                one_row_info.append(rowindex - 1)
                try:
                    locator = "div[aria-rowindex='{}']>div".format(rowindex)
                    before_page_source = self._driver.page_source
                    cells = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)

                    for i_try_get_cell in range(3):
                        logger.debug("获取的表头个数为：{},获取的对应行列数为：{}".format(len(self.all_headers), len(cells)))
                        if len(cells) < len(self.all_headers):  # 当表格中某行的列数和表头列数不相等时，则表示页面有刷新，需要重新设置宽度
                            # self._driver.execute_script("arguments[0].__vue__.width='100000000';", self.ag_table)
                            logger.debug("重新获取元素信息")
                            # 为了获取到全行数据，做的表格相关设置
                            self._set_table_width()
                            # 重新获取单元格
                            cells = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, locator,
                                                                                   timeout=0.5)
                            if self.__get_header_rows() == 2:
                                self.get_group_headers()
                            else:
                                self.get_table_headers()
                        else:
                            break

                    for cell in cells:
                        # current_colindex = current_first_colindex + column_num
                        one_row_info.append(cell.get_attribute("innerText"))

                    logger.debug("获取表格第{}行的数据为：{}".format(rowindex, one_row_info))
                    all_row_info.append(one_row_info)
                    break
                except Exception as e:
                    if "Message: stale element reference: element is not attached to the page document" in str(e):
                        # 异常重试
                        # logger.debug("Exception:{}".format(str(e)))
                        # 为了获取到全行数据，做的表格相关设置
                        self._set_table_width()
                        if self.__get_header_rows() == 2:
                            self.get_group_headers()
                        else:
                            self.get_table_headers()
                        error_message = e
                    else:
                        raise CsClientError(e)
            else:
                raise CsClientError(error_message)

        return all_row_info

    def _get_cell_height(self, cells):
        if len(cells) > 0:
            cell_height = self.__get_element_height(cells[0].find_element_by_attribute(By.XPATH, "./.."))
        else:
            cell_height = 20
        if not cell_height:
            cell_height = 20
        return cell_height

    def _get_one_row_cells(self, colindex):
        # 获取某一行所有单元格
        cells = []
        for i_get_cells in range(3):  # 因为有时获取到的cells为空，则此处多获取几次
            locator = "div[role='row'] div[aria-colindex='{}']".format(colindex)
            self._set_table_width()
            cells = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)
            if len(cells) > 0:
                self.cell_height = self._get_cell_height(cells)
                break
            else:
                sleep(1)

        return cells

    def _get_expected_row_indexs_2(self, cells, header_content, colindex, checked_headers, query_row_number=1,
                                   select_all=False):
        # 在当前页面指定行中所有列的单元格中和期望数据相同的row_index
        current_first_colindex = self.first_column_index
        row_number = 0
        has_find_expected_row = False
        expected_rowindex_list = []
        logger_info = []  # 查找数据是否符合要求显示的日志
        for cell in cells:
            text = cell.get_attribute("innerText")
            row_number = int(cell.find_element_by_attribute(By.XPATH, "./..").get_attribute("row-index"))
            # logger.debug("第{}行{}对应的数据为：{}".format(row_number + 1, checked_headers[0], text))
            if text.strip() == header_content[checked_headers[0]]:
                has_find_expected_row = True
                # 检查此行别的单元格是否符合要求
                other_checked_headers = checked_headers[1:]
                for oh_key in other_checked_headers:
                    # header_index = self.all_headers.index(oh_key)
                    header_index = self._get_header_index(oh_key)
                    colindex = current_first_colindex + header_index
                    locator = "div[row-index='{}'] div[aria-colindex='{}']".format(row_number, colindex)
                    element_text = self.ag_body_center.find_element_by_attribute(By.CSS_SELECTOR, locator,
                                                                                 timeout=0.5).get_attribute("innerText")
                    # logger.info("element_text:{}".format(element_text))
                    if element_text.strip() != header_content[oh_key]:
                        # 不相等则检查下一行
                        has_find_expected_row = False
                        message = "第{}行-表头：{}-期望数据为：{}，实际数据为:{}".format(row_number, oh_key, header_content[oh_key],
                                                                        element_text.strip())
                        logger_info.append([row_number, "error", message])
                        # break
                    else:
                        message = "第{}行-表头：{}-期望数据为：{}，实际数据为:{}".format(row_number, oh_key, header_content[oh_key],
                                                                        element_text.strip())
                        logger_info.append([row_number, "info", message])

            if has_find_expected_row:
                expected_rowindex_list.append(row_number)
                if len(expected_rowindex_list) == query_row_number and not select_all:
                    break
                has_find_expected_row = False  # 当select_all=True时，则需要赋值为False后进行下一轮查找

        if len(expected_rowindex_list) == 0:
            for lf in logger_info:
                logger_level = lf[1]
                message = lf[2]
                if logger_level == "error":
                    logger.error(message)
                else:
                    logger.info(message)

        return expected_rowindex_list

    def _get_header_index(self, header_text):
        # 获取表头在整个表头的索引
        error_msg = ""
        for i_retry in range(3):
            try:
                # 获取到所有要查找的期望的table列
                header_index = self.all_headers.index(header_text)
                return header_index
            except ValueError as e:
                error_msg = "当前表格表头中没有找到'{}'此表头".format(header_text)
                # 重新获取表头信息
                if self.__get_header_rows() == 2:
                    self.get_group_headers()
                else:
                    self.get_table_headers()
        else:
            raise CsClientError(error_msg)

    def get_expected_row_indexs(self, header_content, query_row_number=1, select_all=False):
        '''
        获取符合要求的单元格行号

        :Example:
         - 例: ag_table.get_expected_rows({"产品编号":"438"})
        '''
        # # 获取到期望的行
        # expect_data_list = []
        # header_colindex_list = []
        # query_header_info_list = {}  #
        expected_rowindex_list = []  # 用于存储符合要求的数据行号
        # expected_rowindex_info = {}  # 用于存储符合要求的数据信息

        # 获取table的viewport中第一行行号
        # first_rowindex = self.first_row_index

        current_first_colindex = self.first_column_index

        checked_headers = list(header_content.keys())

        self.expect_row_indexes = []
        self.cell_height = 20

        header_index = self._get_header_index(checked_headers[0])

        colindex = current_first_colindex + header_index

        cells = []
        for i_h_scroll_time in range(3):  # 向下滚动最多滚动三次
            cells = self._get_one_row_cells(colindex)

            error_msg = ""
            for i_retry in range(3):
                try:
                    expected_rowindex_list = self._get_expected_row_indexs_2(cells, header_content, colindex,
                                                                             checked_headers,
                                                                             query_row_number=query_row_number,
                                                                             select_all=select_all)
                    break
                except Exception as e:
                    # 发生异常最多重试三次
                    error_msg = str(e)
            else:
                raise SeleniumError(error_msg)
            if (len(expected_rowindex_list) == 0 and len(cells) > 25) or select_all:  # 暂停当页面显示行数大于25行时则表示需要滚动
                self.__scroll_height_scoller(len(cells) * self.cell_height)
            else:
                break
        return expected_rowindex_list

    def get_table_content(self, expected_rowindex_list):
        '''
        获取到表体的内容

        :Example:
         - 例: ag_table.get_table_content()
        '''
        all_row_info = []
        for rowindex in expected_rowindex_list:
            one_row_info = []  # 列表的第一个值为行号，除第一个值之外的值为行的内容
            one_row_info.append(rowindex - 1)
            locator = "div[row-index='{}']>div".format(rowindex)
            cells = self.ag_body_center.find_elements_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)
            for cell in cells:
                # current_colindex = current_first_colindex + column_num
                one_row_info.append(cell.get_attribute("innerText"))

            logger.debug("获取表格第{}行的数据为：{}".format(rowindex, one_row_info))
            all_row_info.append(one_row_info)

        return all_row_info

    def _check_full_data(self, expected_data_list, actual_data_list, tablename="table", check_type="tablecheck_full"):
        '''
        实际结果与期望结果的比较
        expected_data_list的数据格式为：[{'指令批号': '332', '指令序号': '331'},{'指令批号': '331', '指令序号': '331'}]
        actual_data_list的数据格式为：[{'指令批号': '332', '指令序号': '331'},{'指令批号': '331', '指令序号': '331'}]
        '''
        logger.debug(expected_data_list)
        logger.debug(actual_data_list)
        error_header = []
        error_list = []
        error = False
        for i, expected_data in enumerate(expected_data_list):
            actual_data = actual_data_list[i]
            row_index = actual_data[0]  # 第一个值为行号
            actual_data = actual_data[1:]  # 其他的值为行内容
            for key, expected_value in expected_data.items():
                if expected_value.lower().strip() == "nocheck":
                    # 不需要检查
                    continue

                try:
                    expected_value_colindex = self.all_headers.index(key)
                except Exception as e:
                    error_header.append(key)
                    continue

                try:
                    actual_value = actual_data[expected_value_colindex]
                except Exception as e:
                    # logger.debug("key:{},expected_value_colindex:{}".format(key, expected_value_colindex))
                    message = "在{}数据中并没有找到index为：{}的值".format(actual_data, expected_value_colindex)
                    raise SeleniumError(CustomError.Error_EQ03010000_Verify_Data, message)

                if actual_value:
                    actual_value = actual_value.strip()
                    # 负数需要去掉“-”进行判断
                    if actual_value.replace(',', '').replace(".", "").strip('-').isdigit():
                        actual_value = actual_value.replace(',', '')

                if actual_value.strip() == expected_value.strip():
                    logger.info("检查表格‘{}’中第{}行- 表头为“{}”的数据 -- 实际数据为:{},期望数据为：{}".format(tablename, row_index,
                                                                                        key,
                                                                                        actual_value,
                                                                                        expected_value))
                else:
                    logger.error("检查表格‘{}’中第{}行- 表头为“{}”的数据 -- 实际数据为:{},期望数据为：{}".format(tablename, row_index,
                                                                                         key,
                                                                                         actual_value,
                                                                                         expected_value))
                    error_list.append(key)
                    error = True

        if len(error_header) != 0 and check_type is "tablecheck_full":
            message = "请确认表头信息：‘{}’是否正确".format(error_header)
            raise SeleniumError(CustomError.Error_EQ03007007_Table_Header_Not_Correct, message)

        if error:
            message = "校验数据失败" + str(error_list)
            raise SeleniumError(CustomError.Error_EQ03007002_Table_Verify_Data_Fail, message)

    def __scroll_horizontal_scoller(self, width):
        # 滚动ag-table的横向滚动条
        # locator = ".ag-body-horizontal-scroll .ag-body-horizontal-scroll-viewport"
        # scroll_element = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=self.timeout)
        scroll_element = self.scroll_element
        scroll_element_width = scroll_element.size["width"]
        scroll_width = int(scroll_element_width / 2)
        self._driver.execute_script("arguments[0].scrollLeft={};".format(width), scroll_element)
        if width == 0:
            self._driver.execute_script("arguments[0].scrollLeft={};".format(100), scroll_element)
            sleep(1)
            self._driver.execute_script("arguments[0].scrollLeft={};".format(width), scroll_element)

    def __scroll_height_scoller(self, height):
        # 滚动ag-table的竖向滚动条
        locator = ".ag-body-viewport"
        scroll_element = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=self.timeout)
        self._driver.execute_script("arguments[0].scrollTop={};".format(height), scroll_element)
        sleep(1)

    def __get_height_scoller_height(self):
        locator = ".ag-body-viewport"
        scroll_element = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=self.timeout)
        scroll_element_height = scroll_element.size["height"]
        return int(scroll_element_height)

    def __get_scroll_height_top(self):
        locator = ".ag-body-viewport"
        scroll_element = self.ag_table.find_element_by_attribute(By.CSS_SELECTOR, locator, timeout=0.5)
        return self._driver.execute_script("return arguments[0].scrollTop;", scroll_element)

    def __get_element_style(self, element):
        style = element.get_attribute("style")
        width = 0
        left = 0
        height = 0
        style_split = style.split(";")
        for temp in style_split:
            if "width" in temp:
                width = int(float(temp.replace("width:", '').replace("px", '').strip()))
            elif temp.strip().startswith("left"):
                left = int(float(temp.replace("left:", '').replace("px", '').strip()))
            elif temp.strip().startswith("height"):
                height = int(float(temp.replace("height:", '').replace("px", '').strip()))

        return (width, left, height)

    def __get_element_height(self, element):
        style = element.get_attribute("style")
        style_split = style.split(";")
        for temp in style_split:
            if "height" in temp:
                return int(temp.replace("height:", '').replace("px", ''))

    def __get_translate_y(self, element):
        style = element.get_attribute("style")
        style_split = style.split(";")
        for temp in style_split:
            if "transform" in temp:
                return int(temp.replace("transform: translateY(", '').replace("px)", '').strip())

    def __get_element_colns_colindex(self, element):
        colspan = element.get_attribute("aria-colspan")
        colindex = element.get_attribute("aria-colindex")
        return (colspan, colindex)

    def __get_pinned_headers(self):
        locator = ".ag-header-row-column .ag-header-cell"
        elements = self.ag_pinned_left_header.find_elements_by_attribute(By.CSS_SELECTOR, locator, timeout=1)
        return len(elements)

    def __get_elements_text(self, elements):
        texts = []
        for e in elements:
            texts.append(e.get_attribute("innerText"))
        return texts

    def __get_elements_text_row_index(self, elements):
        text_row_index = []
        for e in elements:
            # 获取到行号
            row_element = e.find_element_by_attribute(By.XPATH, ".//ancestor::div[@role='row']")
            row_index = row_element.get_attribute("row-index")
            text_row_index.append((e.get_attribute("innerText").strip(), row_index))
        return text_row_index

    def __get_current_headers(self):
        # 获取到目前屏幕可见的表头信息
        header_cell_locator = ".ag-header-cell .ag-header-cell-text"
        header_cell_texts = self.ag_header_viewport.find_elements_by_attribute(By.CSS_SELECTOR, header_cell_locator)
        header_texts = self.__get_elements_text(header_cell_texts)
        return (header_texts, header_cell_texts)

    def __get_group_current_headers(self):
        group_header_cell_locator = ".ag-header-row-column-group .ag-header-group-cell .ag-header-group-text"
        group_header_cell_texts = self.ag_header_viewport.find_elements_by_attribute(By.CSS_SELECTOR,
                                                                                     group_header_cell_locator)
        group_header_texts = self.__get_elements_text(group_header_cell_texts)
        return (group_header_texts, group_header_cell_texts)





